/*
 *  ckr_spec_context_pass_fail_time.cpp
 *  ckrSpec
 *
 *  Created by C. Keith Ray on 2006.11.07.
 *  Copyright 2006 __MyCompanyName__. All rights reserved.
 *
 */

#import "ckr_spec_cpp_specs.h"
#import "ckr_spec_cpp.h"

#include <iostream>
#include <sstream>
#include <vector>
#include <deque>
#include <algorithm>

#include <assert.h>

using namespace std;
using namespace ckr;

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

static void contextList_can_be_sorted_by_name();
static void context_should_record_duration();
static void contextList_can_be_sorted_by_duration();

static void context_should_record_pass_fail();
static void specrunner_contains_summary_report();

static void specrunner_runs_fast_only();
static void specrunner_runs_new_first();

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

void run_context_pass_fail_time_specs()
{
	contextList_can_be_sorted_by_name();
	context_should_record_duration();
	contextList_can_be_sorted_by_duration();

	context_should_record_pass_fail();
	specrunner_contains_summary_report();

	specrunner_runs_fast_only();
	specrunner_runs_new_first();
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

class AngelinaContext : public Context
{
public: 
	AngelinaContext() : Context( "AngelinaContext", __LINE__, __FILE__ )
	{ };
};

class BradContext : public Context
{
public: 
	BradContext() : Context( "BradContext", __LINE__, __FILE__ )
	{ };

	void spec()
	{
		FAIL("He's not good enough for her");
	}
};

class JanetContext : public Context
{
public: 
	JanetContext() : Context( "JanetContext", __LINE__, __FILE__ )
	{ };
};

static void contextList_can_be_sorted_by_name()
{
	ContextList aContextList;
	BradContext aBrad;
	AngelinaContext anAngelina;
	JanetContext aJanet;

	assert( aBrad.name() == "BradContext" );

	aContextList.push_back( & aJanet );
	aContextList.push_back( & anAngelina );
	aContextList.push_back( & aBrad );
	
	sort( aContextList.begin(), aContextList.end(), ContextNameLessThan );
	
	assert( aContextList.at(0) == & anAngelina );
	assert( aContextList.at(1) == & aBrad );
	assert( aContextList.at(2) == & aJanet );
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

class TimedContext : public Context
{
public: 
	TimedContext() : Context( "TimedContext", __LINE__, __FILE__ )
	{ };

	virtual void spec()
	{
		const unsigned long kMillion = 1000000UL;

		timespec nanu = { 0, 0 };
		timespec nanuNanu = { 0, kMillion / 100UL };

		::nanosleep( &nanu, &nanuNanu );  // sleep approx 1/100 of a second
	}
};

static void context_should_record_duration()
{
	TimedContext aTimedContext;
	
	assert( aTimedContext.duration() == 0.0 );

	SpecListener  aListener;
	aTimedContext.run_spec( & aListener );

	assert( aTimedContext.duration() > 0.0 );
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

class ShortTimedContext : public Context
{
public: 
	ShortTimedContext() : Context( "ShortTimedContext", __LINE__, __FILE__ )
	{ };

	virtual double duration() const
	{
		return 0.01;
	}
};

class MediumTimedContext : public Context
{
public: 
	MediumTimedContext() : Context( "MediumTimedContext", __LINE__, __FILE__ )
	{ };

	virtual double duration() const
	{
		return 0.1;
	}
};

class LongTimedContext : public Context
{
public: 
	LongTimedContext() : Context( "LongTimedContext", __LINE__, __FILE__ )
	{ };

	virtual double duration() const
	{
		return 1.0;
	}
};

static void contextList_can_be_sorted_by_duration()
{
	MediumTimedContext	medCon;
	LongTimedContext		longCon;
	ShortTimedContext		shortCon;

	ContextList aContextList;
	aContextList.push_back( & medCon );
	aContextList.push_back( & longCon );
	aContextList.push_back( & shortCon );

	sort( aContextList.begin(), aContextList.end(), ContextDurationLessThan );
	
	assert( aContextList.at(0) == & shortCon );
	assert( aContextList.at(1) == & medCon );
	assert( aContextList.at(2) == & longCon );
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

static void context_should_record_pass_fail()
{
	AngelinaContext aPassingContext;
	
	assert( aPassingContext.hasBeenRun() == false );
	assert( aPassingContext.hasFailed() == false );
	
	SpecListener  aListener;
	aPassingContext.run_spec( & aListener );
	
	assert( aPassingContext.hasBeenRun() == true );
	assert( aPassingContext.hasFailed() == false );
	
	BradContext aFailingContext;
	aFailingContext.run_spec( & aListener );
	
	assert( aFailingContext.hasBeenRun() == true );
	assert( aFailingContext.hasFailed() == true );
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

/*
	there's a lot of duplication in all those failure methods, each of which
	is setting  myHasFailed = true.
	
	time to refactor to something...
	
		1. generate failure information (an object)
		2. bottle-neck: we notify listener of failure (default listener uses a stream)
		3. default listener writes failure message from failure information onto stream.
*/

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

static void specrunner_contains_summary_report()
{
	SpecRunner				aRunner;
	AngelinaContext		aPassingContext;
	BradContext				aFailingContext;
	MediumTimedContext	medCon;
	LongTimedContext		longCon;
	ShortTimedContext		shortCon;

	aRunner.add( & aPassingContext );
	aRunner.add( & aFailingContext );
	aRunner.add( & medCon );
	aRunner.add( & longCon );
	aRunner.add( & shortCon );

	assert( 0 == aRunner.numSpecsPassing() );
	assert( 0 == aRunner.numSpecsFailing() );
	
	ContextList passing = aRunner.passingSpecs();
	assert( 0 == passing.size() );

	ContextList failing = aRunner.failingSpecs();
	assert( 0 == failing.size() );

	double dur = aRunner.totalSpecDuration();
	assert( 0.0 == dur );

	SpecListener listener;
	aRunner.runAll( & listener );

	assert( 4 == aRunner.numSpecsPassing() );
	assert( 1 == aRunner.numSpecsFailing() );
	
	passing = aRunner.passingSpecs();
	assert( 4 == passing.size() );
	assert( passing.end() != std::find( passing.begin(), passing.end(), 
		& aPassingContext ) );
	assert( passing.end() != std::find( passing.begin(), passing.end(), 
		& medCon ) );
	assert( passing.end() != std::find( passing.begin(), passing.end(), 
		& longCon ) );
	assert( passing.end() != std::find( passing.begin(), passing.end(), 
		& shortCon ) );

	failing = aRunner.failingSpecs();
	assert( 1 == failing.size() );
	assert( & aFailingContext == failing.at(0) );

	dur = aRunner.totalSpecDuration();

	double expectedDur = aPassingContext.duration();
	expectedDur += aFailingContext.duration();
	expectedDur += medCon.duration();
	expectedDur += longCon.duration();
	expectedDur += shortCon.duration();

	assert( fabs(dur - expectedDur) < 0.0001  ); // assert near-equality
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

static void specrunner_runs_fast_only()
{
	MediumTimedContext	medCon;   // 0.10
	LongTimedContext		longCon;  // 1.00
	ShortTimedContext		shortCon; // 0.01  
	// average duration is 0.37 so only shortCon and medCon should run.

	SpecRunner				aRunner;
	aRunner.add( & medCon );
	aRunner.add( & longCon );
	aRunner.add( & shortCon );

	RecordingStartEndSpecListener listener;
	aRunner.runFastOnly( & listener );

	std::string actual = listener.log.str();
	const std::string expected = "specStarted(ShortTimedContext); "
		"specEnded(ShortTimedContext,1); specStarted(MediumTimedContext); "
		"specEnded(MediumTimedContext,1); ";
	
	assert( expected == actual );
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

static void specrunner_runs_new_first()
{
	MediumTimedContext	medCon;
	LongTimedContext		longCon;
	ShortTimedContext		shortCon;
	
	SpecRunner				aRunner;
	aRunner.add( & medCon );
	aRunner.add( & longCon );
	aRunner.add( & shortCon );

	RecordingStartEndSpecListener listener;

	assert( medCon.hasBeenRun() == false );
	medCon.run_spec( & listener );
	assert( medCon.hasBeenRun() == true );
	
	listener.log.clear();

	aRunner.runNewFirst( & listener );

	std::string actual = listener.log.str();
	const std::string expected = 
		"specStarted(ShortTimedContext); specEnded(ShortTimedContext,1); "
		"specStarted(LongTimedContext); specEnded(LongTimedContext,1); "
		"specStarted(MediumTimedContext); specEnded(MediumTimedContext,1); ";
	
	assert( expected == actual );
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

